#region Using Statements
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Tomahawk.Runtime.Rendering.Interfaces;
using Microsoft.Xna.Framework.Graphics;
using Tomahawk.Runtime.Rendering.Materials;
using Microsoft.Xna.Framework.Content;
using System.Runtime.InteropServices;
using Tomahawk.Runtime.Resources;
#endregion

namespace Tomahawk.Runtime.Rendering.Objects
{
    /// <summary>
    /// This is a renderable primitive the draws a quad using a material
    /// </summary>
    public class MaterialQuad : MaterialEntity, IRenderable
    {
        #region Vertex declaration
        [StructLayout(LayoutKind.Sequential)]
        public struct QuadVertexPositionTexture
        {
            public Vector3 Position;
            public Vector3 UVW;

            public static int SizeInBytes { get { return 6 * sizeof(float); } }
            public static readonly VertexElement[] VertexElements = new VertexElement[]
            {
               new VertexElement(0, 0, VertexElementFormat.Vector3,
                VertexElementMethod.Default, VertexElementUsage.Position, 0),
               new VertexElement(0, sizeof(float) * 3, VertexElementFormat.Vector3,
                VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 0),
            };

            public QuadVertexPositionTexture(Vector3 pos, Vector3 uvw)
            {
                Position = pos;
                UVW = uvw;
            }
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct QuadVertexPositionTextureNormal
        {
            public Vector3 Position;
            public Vector3 UVW;
            public Vector3 Normal;
            public Vector3 Tangent;
            public Vector3 Binormal;

            public static int SizeInBytes { get { return 15 * sizeof(float); } }
            public static readonly VertexElement[] VertexElements = new VertexElement[]
            {
               new VertexElement(0, 0, VertexElementFormat.Vector3,
                VertexElementMethod.Default, VertexElementUsage.Position, 0),
               new VertexElement(0, sizeof(float) * 3, VertexElementFormat.Vector3,
                VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 0),
               new VertexElement(0, sizeof(float) * 6, VertexElementFormat.Vector3,
                VertexElementMethod.Default, VertexElementUsage.Normal, 0),
               new VertexElement(0, sizeof(float) * 9, VertexElementFormat.Vector3,
                VertexElementMethod.Default, VertexElementUsage.Tangent, 0),
               new VertexElement(0, sizeof(float) * 12, VertexElementFormat.Vector3,
                VertexElementMethod.Default, VertexElementUsage.Binormal, 0),
            };

            public QuadVertexPositionTextureNormal(Vector3 pos, Vector3 uvw, Vector3 normal, Vector3 tangent, Vector3 binormal)
            {
                Position = pos;
                UVW = uvw;
                Normal = normal;
                Tangent = tangent;
                Binormal = binormal;
            }
        }
        #endregion

        public enum QuadVertexFormat
        {
            PositionTexture = 0,
            PositionTextureNormal = 1,
        }

        public class MaterialQuadDefinition
        {
            public QuadVertexFormat format = QuadVertexFormat.PositionTextureNormal;
            public string materialName = null;
            public string diffuseMapName = null;
            public string normalMapName = null;
            public string specularMapName = null;
            public string environmentMapName = null;
            public string lightMapName = null;
        };
        private MaterialQuadDefinition materialDefinition;

        private VertexBuffer myVertices;
        public VertexBuffer Vertices { get { return myVertices; } }
        
        private IndexBuffer myIndices;
        public IndexBuffer Indices { get { return myIndices; } }

        private VertexDeclaration declaration;
        public VertexDeclaration Declaration { get { return declaration; } }

        protected SASMaterial material;

        protected RenderManager.Layers layer = RenderManager.Layers.Standard;
        public RenderManager.Layers Layer { get { return layer; } set { layer = value; } }

        RenderCommand.ExecuteCommandDelegate ExecuteRenderCommandDelegate = null;

        /// <summary>
        /// Width of the texture used in the Diffuse Map.
        /// </summary>
        public float DiffuseMapWidth
        {
            get
            {
                if (material != null)
                {
                    Texture2D tex = material.DiffuseMap as Texture2D;
                    if (tex != null)
                    {
                        return tex.Width;
                    }
                }

                return 0.0f;
            }
        }

        /// <summary>
        /// Height of the texture used in the Diffuse Map.
        /// </summary>
        public float DiffuseMapHeight
        {
            get
            {
                if (material != null)
                {
                    Texture2D tex = material.DiffuseMap as Texture2D;
                    if (tex != null)
                    {
                        return tex.Height;
                    }
                }

                return 0.0f;
            }
        }

        /// <summary>
        /// Constructor
        /// </summary>
        public MaterialQuad(RenderManager.Layers layer, MaterialQuadDefinition materialDefinition, ResourceManager.ResourcePool pool)
        {
            this.resourcePool = pool;

            this.layer = layer;
            this.ExecuteRenderCommandDelegate = new RenderCommand.ExecuteCommandDelegate(OnExecuteRenderCommand);

            this.materialDefinition = new MaterialQuadDefinition()
            {
                format = materialDefinition.format,
                materialName = materialDefinition.materialName,
                diffuseMapName = materialDefinition.diffuseMapName,
                normalMapName = materialDefinition.normalMapName,
                specularMapName = materialDefinition.specularMapName
            };
        }

        /// <summary>
        /// Load Content: get the material, and create the quad mesh
        /// </summary>
        protected override bool OnLoad(GraphicsDevice graphics, ContentManager contentManager)
        {
            material = Engine.Instance.MaterialManager.Load<SASEffect>(this.materialDefinition.materialName, contentManager);

            if (material == null)
            {
                throw new Exception("The mesh references an invalid material");
            }

            if ((materialDefinition.diffuseMapName != null) && (materialDefinition.diffuseMapName != ""))
            {
                material.DiffuseMap = contentManager.Load<Texture>(materialDefinition.diffuseMapName);
            }

            if ((materialDefinition.normalMapName != null) && (materialDefinition.normalMapName != ""))
            {
                material.NormalMap = contentManager.Load<Texture>(materialDefinition.normalMapName);
            }

            if ((materialDefinition.specularMapName != null) && (materialDefinition.specularMapName != ""))
            {
                material.SpecularMap = contentManager.Load<Texture>(materialDefinition.specularMapName);
            }

            if ((this.materialDefinition.environmentMapName != null) && (materialDefinition.environmentMapName != ""))
            {
                material.EnvironmentMap = contentManager.Load<Texture>(materialDefinition.environmentMapName);
            }

            if ((this.materialDefinition.lightMapName != null) && (materialDefinition.lightMapName != ""))
            {
                material.LightMap = contentManager.Load<Texture>(materialDefinition.lightMapName);
            } 

            //create the quad
            lock (Engine.ResourceLock)
            {
                switch (this.materialDefinition.format)
                {
                    case QuadVertexFormat.PositionTexture:
                        {
                            QuadVertexPositionTexture[] verts = {
		                        new QuadVertexPositionTexture(new Vector3(-0.5f, 0.5f, 0), new Vector3(0, 0, 0)),
		                        new QuadVertexPositionTexture(new Vector3(0.5f, 0.5f, 0), new Vector3(1, 0, 0)),		
		                        new QuadVertexPositionTexture(new Vector3(-0.5f, -0.5f, 0), new Vector3(0, 1, 0)),	
		                        new QuadVertexPositionTexture(new Vector3(0.5f, -0.5f, 0), new Vector3(1, 1, 0))
	                        };

                            myVertices = new VertexBuffer(
                                graphics, typeof(QuadVertexPositionTexture), 4,
                                BufferUsage.WriteOnly);

                            myVertices.SetData<QuadVertexPositionTexture>(verts, 0, 4);
                            declaration = new VertexDeclaration(graphics, QuadVertexPositionTexture.VertexElements);
                        } break;
                    case QuadVertexFormat.PositionTextureNormal:
                        {
                            QuadVertexPositionTextureNormal[] verts = {
		                        new QuadVertexPositionTextureNormal(new Vector3(-0.5f, 0.5f, 0), new Vector3(0, 0, 0), new Vector3(0, 0, 1), new Vector3(1, 0, 0), new Vector3(0, 1, 0)),
		                        new QuadVertexPositionTextureNormal(new Vector3(0.5f, 0.5f, 0), new Vector3(1, 0, 0), new Vector3(0, 0, 1), new Vector3(1, 0, 0), new Vector3(0, 1, 0)),		
		                        new QuadVertexPositionTextureNormal(new Vector3(-0.5f, -0.5f, 0), new Vector3(0, 1, 0), new Vector3(0, 0, 1), new Vector3(1, 0, 0), new Vector3(0, 1, 0)),	
		                        new QuadVertexPositionTextureNormal(new Vector3(0.5f, -0.5f, 0), new Vector3(1, 1, 0), new Vector3(0, 0, 1), new Vector3(1, 0, 0), new Vector3(0, 1, 0))
	                        };

                            myVertices = new VertexBuffer(
                                graphics, typeof(QuadVertexPositionTextureNormal), 4,
                                BufferUsage.WriteOnly);

                            myVertices.SetData<QuadVertexPositionTextureNormal>(verts, 0, 4);
                            declaration = new VertexDeclaration(graphics, QuadVertexPositionTextureNormal.VertexElements);
                        } break;

                    default:
                        {
                            throw new Exception("Invalid vertex format for quad");
                        }
                }

                int[] inds = { 0, 1, 2, 1, 3, 2 };

                myIndices = new IndexBuffer(
                    graphics, typeof(int), 6,
                    BufferUsage.WriteOnly);

                myIndices.SetData<int>(inds, 0, 6);
            }

            return true;
        }

        /// <summary>
        /// Prepares the material for rendering 
        /// </summary>
        public void OnExecuteRenderCommand(SASMaterial material)
        {
            RaiseOnPrepareMaterial(this, material);
        }

        /// <summary>
        /// Render the quad
        /// </summary>
        public void Render(ref Matrix worldMatrix, GraphicsDevice myDevice)
        {
            if (material.Effect.IsTechniqueAvailable(SASEffect.currentRenderGroup))
            {
                RenderManager manager = Engine.Instance.RenderManager;

                RenderCommand command = Engine.Instance.RenderManager.BuildRenderCommand(this, ref worldMatrix, material);
                command.OnExecuteCommand += ExecuteRenderCommandDelegate;
                manager.AddCommand(command, layer);
            }
        }
    }
}


